package game

import (
	"World/common"
	"World/common/logger"
	"World/db"
	"World/pb"
	pmgr "World/playermgr"
	"World/utils"
	"bytes"
	"crypto/md5"
	"fmt"
	"strconv"

	"encoding/binary"

	"github.com/gin-gonic/gin"
	"github.com/golang/protobuf/proto"

	"io"
	"log"
	"net"
	"net/http"
	"os"
	"time"
)

const (
	HeartbeatInterval = 600 * time.Second
	ConnectionTimeout = 2000 * time.Second
)

func ConnWriter(player *common.GamePlayer) {
	if player == nil {
		logger.Info("conn writer routine start failed with nil player")
		return
	}
	player.LastAt = time.Now().Unix()
	logger.Info("conn writer routine start uid:%v seq:%v", player.UID, player.ConnSeq)
	//ticker := time.NewTicker(HeartbeatInterval)
	for {
		select {
		/*case <-ticker.C:
		deadline := time.Now().Add(-ConnectionTimeout).Unix()
		if player.LastAt < deadline {
			//心跳超时处理
			if player.Conn != nil {
				//通知客户端断开
				var msg pb.WorldNoticeConnClosed
				msg.PlayerId = int32(player.UID)
				common.SendNoti(player, 0, uint32(player.UID), pb.MSGID_WORLD_MsgID_ConnClose_Notice, &msg)
				logger.Notic("NoticeConnClosed,uid:%v", player.UID)
				//执行关闭操作
				err := player.Conn.Close()
				if err == nil {
					logger.Info("ConnWriter player close conn uid: %v", player.UID)
				} else {
					logger.Error("ConnWriter close conn err:%v, uid:%v", err, player.UID)
				}
			}
			//更新用户的在线状态
			//db.UpdatePlayerOnlineState(uint32(player.UID), 0)
			return
		}*/
		//主动回复心跳
		/*var resp pb.ResponseHeartBeat
		resp.Uid = int32(player.UID)
		header := pb.MessageHeader{0, 0, 0, 0, uint32(player.UID), 0}
		SendMessage(player, player.Conn, header, pb.MSGID_MsgID_HeartBeat_Response, &resp)
		logger.Notic("HeartBeatHandler resp:uid:%v", resp.Uid)*/
		case data := <-player.OutChannel:
			//todo send data to conn
			player.ConnLocker.Lock()
			conn := player.Conn
			player.ConnLocker.Unlock()

			WriteMessage2Conn(player.UID, conn, data)
		case _ = <-player.OutDone:
			logger.Info("conn writer routine end 2 uid:%v seq:%v", player.UID, player.ConnSeq)
			return
		}
	}
	logger.Info("conn writer routine end uid:%v seq:%v", player.UID, player.ConnSeq)
}

func WriteMessage2Conn(uid int, conn net.Conn, data []byte) {
	if conn == nil {
		return
	}
	conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
	_, err := conn.Write(data)
	if err != nil {
		logger.Notic("send socket messeage error:%v uid:%v ip:%v", err, uid, conn.RemoteAddr().String)
	} else {
		//logger.Info("send socket message success for uid:%v data:%s", uid, conn.RemoteAddr().String())
	}
}

func Cors() gin.HandlerFunc {
	return func(context *gin.Context) {
		method := context.Request.Method
		context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		context.Header("Access-Control-Allow-Origin", "*") // 设置允许访问所有域
		context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
		context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken")
		context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")

		if method == "OPTIONS" {
			context.JSON(http.StatusOK, gin.H{"code": 1, "msg": "Options Request!"})
			return
		}
		context.Next()
	}
}

func Run() {
	f, err := os.Create("../log/http_world.log")
	if err != nil {
		log.Fatalln(err)
	}
	gin.DefaultWriter = io.MultiWriter(f)
	log.Println(f)
	log.Println("../log/http_world.log")

	router := gin.Default()
	router.Use(Cors())
	//router.POST("/requestLogin", LoginReq)

	router.Run(":8000")
}

func SendMessage(player *common.GamePlayer, conn net.Conn, header pb.MessageHeader, msgid pb.MSGID, msg interface{}) bool {
	if conn == nil {
		logger.Notic("send message failed with nil conn, playerid:%v msgid:%v", header.PlayerID, msgid)
		return false
	}
	tmp, err := proto.Marshal(msg.(proto.Message))
	if err != nil {
		logger.Notic("send message marshal msgid:%v response body error:%v", msgid, err)
		return false
	}

	if player != nil {
		if !common.IsPlayerInPlayerMap(player.UID) {
			logger.Notic("send message player not in map, msgid:%v response body error:%v", msgid, err)
			return false
		}
	}

	if pb.MSGID(msgid) != 0 /*pb.MSGID_MsgID_HeartBeat_Response*/ {
		logger.Info("send message response uid:%v msgid:%+v", header.PlayerID, pb.MSGID(msgid))
	}
	/*var data []byte
	if msgid == pb.MSGID_MsgID_Logon_Response {
		logger.Notic("send message response logon, uid:%v, msg:%+v", header.PlayerID, msg)
		//rsa加密
		data, err = gorsa.RSA.PriKeyENCTYPT(tmp)
		if err != nil {
			logger.Notic("send message rsa encrypted msgid:%v  response body error:%v", msgid, err)
			return false
		}
	} else {
		//aes加密
		aesData, err := goEncrypt.AesCbcEncrypt(tmp, conf.EncryptCfg.AesKey, conf.EncryptCfg.AesIv...)
		if err != nil {
			logger.Notic("send message aes encrypted msgid:%v  response body error:%v", msgid, err)
			return false
		}
		bsData := base64.StdEncoding.EncodeToString(aesData)
		data = []byte(bsData)
	}*/
	data_len := len(tmp) + int(common.Min_Message_Size)
	//total_len := len(data) + int(common.Min_Message_Size)
	bys := new(bytes.Buffer)
	//binary.Write(bys, binary.BigEndian, uint16(total_len))
	binary.Write(bys, binary.BigEndian, uint16(data_len))
	binary.Write(bys, binary.BigEndian, uint16(msgid))
	binary.Write(bys, binary.BigEndian, uint32(header.Seq))
	binary.Write(bys, binary.BigEndian, uint32(header.PlayerID))
	binary.Write(bys, binary.BigEndian, uint32(header.ErrorCode))

	buf := append(bys.Bytes(), tmp...)

	if player != nil {
		select {
		case player.OutChannel <- buf:
			break
		case <-time.After(time.Millisecond * 20):
			logger.Notic("send message to uid:%v roomid:%v timeout, channel full", header.PlayerID, header.ErrorCode)
			return false
		}
	} else {
		//WriteMessage2Conn(player.UID, conn, buf)
		conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
		_, err := conn.Write(buf)
		if err != nil {
			logger.Notic("send message send socket messeage error:%v ip:%v", err, conn.RemoteAddr().String)
		}
	}

	return true
}

func GenerateToken() string {
	crutime := time.Now().Unix()

	h := md5.New()

	io.WriteString(h, strconv.FormatInt(crutime, 10))

	token := fmt.Sprintf("%x", h.Sum(nil))
	fmt.Println("token--->", token)

	return token
}

func ConnCloseHandler(conn net.Conn, header pb.MessageHeader, msg interface{}) {
	//捕获异常
	defer utils.PrintPanicStack()
	//处理逻辑
	req := msg.(*pb.WorldNoticeConnectionClose)
	logger.Info("ConnCloseHandler-connection closed:%v", req)
	player, er := pmgr.GetPlayerFromMap(int(header.PlayerID))
	if er != nil {
		return
	}
	if player.ConnSeq != header.Seq || (player.ConningSeq > 0 && (player.ConningSeq != header.Seq)) {
		logger.Info("conn close, but seq not match, player valid connseq:%v closing seq:%d conning seq:%v for uid:%v",
			player.ConnSeq, header.Seq, player.ConningSeq, header.PlayerID)
		return
	}
	if player.OutDone != nil {
		logger.Info("conn close, outdone channel close for uid:%v", header.PlayerID)
		close(player.OutDone)
		player.OutDone = nil
	}

	pmgr.RemovePlayer(int(header.PlayerID))
	logger.Info("ConnCloseHandler-pmgr.RemovePlayer uid: %v", header.PlayerID)

	common.PlayerMap.Delete(header.PlayerID)

	logger.Info("player close outdone channel uid: %v", header.PlayerID)
	//更新用户的在线状态
	//db.UpdatePlayerOnlineState(header.PlayerID, 0)
}

func LoginHandler(conn net.Conn, header pb.MessageHeader, msg interface{}) {
	req := msg.(*pb.WorldLogonReq)
	logger.Info("****login req header:%+v body:%+v", header, req)
	var err_num common.ErrorType = common.Error_OK

	var resp pb.WorldLogonRsp

	var player *common.GamePlayer
	for {
		//首先判断该账号是否存在
		uuid, err := db.GetUuid(req.DeviceUuid)
		if uuid == 0 || err != nil {
			//该账号不存在  重新注册
			uuid, err = db.Register(req.DeviceUuid)
			if err != nil || uuid == 0 {
				logger.Error("LoginHandler regeister failed")
				err_num = common.Error_Reg_Failed
				break
			}
		} else {
			//更新登录时间
			err = db.UpdateLogin(uuid)
			if err != nil {
				logger.Error("LoginHandler UpdateLogin failed")
			}
		}
		resp.Uuid = int32(uuid)

		//uuid获取到以后
		player, err_num = pmgr.GetPlayer(uint32(uuid))
		if err_num != common.Error_OK || player == nil {
			err_num = common.Error_Player_Not_Found
			logger.Notic("get player uid:%v failed:%v seq:%v", header.PlayerID, err_num, header.Seq)
			break
		}
		player.ConningSeq = header.Seq

		//update conn into player
		player.UpdateConn(conn)

		//需要更新token
		Token := GenerateToken()
		resp.Token = Token
		err = db.UpdateToken(Token, uuid)
		if err != nil {
			err_num = common.Error_Op_Db_Failed
			logger.Notic("get player uid:%v failed:%v seq:%v", header.PlayerID, err_num, header.Seq)
			break
		}
		//需要获取url
		resp.GameUrl, err = db.GetGameUrl(req.GameId)
		if err != nil {
			err_num = common.Error_Op_Db_Failed
			logger.Notic("get player uid:%v failed:%v seq:%v", header.PlayerID, err_num, header.Seq)
			break
		}

		player.ConnSeq = header.Seq
		break
	}

	if player != nil {
		player.ConningSeq = 0
	}

	if err_num == common.Error_OK {
		vv := common.PlayerMap.Get(uint32(resp.Uuid))
		if vv == nil {
			logger.Info("add player map uid:%d seq:%v", resp.Uuid, header.Seq)
			common.PlayerMap.Set(uint32(resp.Uuid), 1)

			go ConnWriter(player)
		}
		//更新用户的在线状态
		//db.UpdatePlayerOnlineState(header.PlayerID, 1)
	}

	//resp.Error = int32(err_num)
	header.ErrorCode = uint32(err_num)
	resp.Curtime = int32(time.Now().Unix())
	logger.Info("LoginHandler resp=%+v", resp)
	SendMessage(player, conn, header, pb.MSGID_MsgID_SC_World_Logon_Rsp, &resp)

}

func HeartBeatHandler(conn net.Conn, header pb.MessageHeader, msg interface{}) {
	req, ok := msg.(*pb.WorldHeartBeatReq)
	if !ok {
		logger.Notic("error invalid message body, header:%+v", header)
		return
	}
	logger.Info("HeartBeatHandler， req:%+v", req)
	var player *common.GamePlayer
	var err_num common.ErrorType = common.Error_OK
	for {
		player, err_num = pmgr.GetPlayer(header.PlayerID)
		if err_num != common.Error_OK || player == nil {
			err_num = common.Error_Player_Not_Found
			logger.Notic("get player uid:%v failed:%v seq:%v", header.PlayerID, err_num, header.Seq)
			break
		}
		break
	}
	var resp pb.WorldHeartBeatRsp
	resp.Uid = req.Uid
	resp.Curtime = int32(time.Now().Unix())
	logger.Info("HeartBeatHandler resp=%+v", resp)
	SendMessage(player, conn, header, pb.MSGID_MsgID_SC_World_HeartBeat_Rsp, &resp)
}
